package com.rent.house.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.rent.common.constant.CommonConstant;
import com.rent.common.entity.HouseAuditMessageDto;
import com.rent.common.enums.UserRole;
import com.rent.common.exception.BusinessException;
import com.rent.common.interceptor.UserContext;
import com.rent.common.util.RedisUtil;
import com.rent.house.dao.HouseResourceDao;
import com.rent.house.pojo.dto.PendingHouseDto;
import com.rent.house.pojo.entity.HouseResource;
import com.rent.house.pojo.entity.Pic;
import com.rent.house.pojo.vo.HouseResourceVo;
import com.rent.house.service.HouseResourceService;
import com.rent.house.service.PicService;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotBlank;

/**
 * 房源服务实现类
 */
@Service
@RequiredArgsConstructor
public class HouseResourceServiceImpl extends ServiceImpl<HouseResourceDao, HouseResource> implements HouseResourceService {
    private final RedisUtil redisUtil;
    private final RedisTemplate<String, Object> redisTemplate;
    private final PicService picService;
    private final RabbitTemplate rabbitTemplate;

    /**
     * 发布房源
     * @param houseResource 房源信息
     * @param picUrls 图片URL列表
     * @return 房源ID
     */
    @Override
    @Transactional
    public Long publishHouse(HouseResource houseResource, List<String> picUrls) {
        // 1. 验证当前用户是否为房东
        Integer role = UserContext.getRole();
        if (role == null || !Objects.equals(UserRole.LANDLORD.getCode(), role)) {
            throw new BusinessException("只有房东才能发布房源");
        }

        // 2. 设置房东ID
        Long userId = UserContext.getUserId();
        houseResource.setLandlordId(userId);

        // 3. 设置默认状态
        houseResource.setStatus(0); // 待审核
        houseResource.setAuditStatus(0); // 待审核
        houseResource.setAuditTime(null); // 审核时间为空
        houseResource.setAuditUserId(null); // 审核人为空
        houseResource.setViewCount(0); // 初始浏览量为0

        // 4. 保存房源信息
        this.save(houseResource);

        // 5. 保存房源图片信息（如果提供了图片URL列表）
        if (picUrls != null && !picUrls.isEmpty()) {
            picService.saveHousePics(houseResource.getId(), picUrls);
        }

        return houseResource.getId();
    }

    @Override
    public HouseResource getHouseDetail(Long id) {
        // 1. 构造缓存键
        String cacheKey = CommonConstant.CACHE_HOUSE_PREFIX + id;
        String viewCountKey = CommonConstant.CACHE_HOUSE_VIEW_COUNT_PREFIX + id;

        // 2. 尝试从Redis获取缓存的房源信息
        HouseResource house = redisUtil.get(cacheKey, HouseResource.class);
        if (house == null) {
            // 3. 缓存未命中，从数据库查询
            house = this.getById(id);
            if (house != null) {
                // 4. 将房源信息存入Redis缓存（1小时过期）
                redisUtil.set(cacheKey, house, CommonConstant.CACHE_HOUSE_EXPIRE);
            }
        }

        // 5. 增加浏览量计数（Redis中）
        if (house != null) {
            redisUtil.increment(viewCountKey, 1);

            // 6. 获取总浏览量并设置到房源对象中
            Integer viewCount = redisUtil.get(viewCountKey, Integer.class);
            if (viewCount != null) {
                house.setViewCount(viewCount);
            }
        }

        return house;
    }

    @Override
    @Transactional
    public HouseResourceVo getHouseDetailWithPics(Long id) {
        HouseResource house = getHouseDetail(id);
        if (house == null) {
            return null;
        }
        return convertToVo(house);
    }

    @Override
    public Page<HouseResource> listHouses(
            Integer pageNo, Integer pageSize,
            String city, BigDecimal minPrice,
            BigDecimal maxPrice, String roomType,
            String keyword) {

        Page<HouseResource> page = new Page<>(pageNo, pageSize);
        QueryWrapper<HouseResource> wrapper = new QueryWrapper<>();
        wrapper.eq(StringUtils.hasText(city),"city", city)
                .ge(!ObjectUtils.isEmpty(minPrice),"price", minPrice)
                .le(!ObjectUtils.isEmpty(maxPrice),"price", maxPrice)
                .eq(StringUtils.hasText(roomType),"room_type", roomType)
                .and(StringUtils.hasText(keyword),w -> w.like("title", keyword).or().like("description", keyword))
                .eq("audit_status", 1)// 只显示已审核通过的房源
                .eq("status", 1)
                .orderByDesc("create_time");// 按创建时间倒序排列

        this.page(page, wrapper);

        return page;
    }

    @Override
    public Page<HouseResourceVo> listHousesWithPics(Integer pageNo, Integer pageSize, String city, BigDecimal minPrice, BigDecimal maxPrice, String roomType, String keyword) {
        Page<HouseResource> page = listHouses(pageNo, pageSize, city, minPrice, maxPrice, roomType, keyword);

        // 转换为VO对象并加载图片信息
        List<HouseResourceVo> voList = page.getRecords().stream()
                .map(this::convertToVo)
                .collect(Collectors.toList());

        Page<HouseResourceVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        voPage.setRecords(voList);
        return voPage;
    }

    @Override
    public Page<HouseResource> searchHouses(String keyword, Integer pageNo, Integer pageSize) {
        // 1. 构造缓存键
        String cacheKey = CommonConstant.CACHE_HOUSE_SEARCH_PREFIX + keyword + ":page" + pageNo + ":size" + pageSize;

        // 2. 尝试从Redis获取缓存的搜索结果
        Page<HouseResource> cachedPage = redisUtil.get(cacheKey, Page.class);
        if (cachedPage != null) {
            return cachedPage;
        }

        // 3. 缓存未命中，执行数据库查询
        Page<HouseResource> page = new Page<>(pageNo, pageSize);
        QueryWrapper<HouseResource> wrapper = new QueryWrapper<>();

        if (keyword != null && !keyword.isEmpty()) {
            wrapper.and(w -> w.like("title", keyword).or().like("description", keyword));
        }

        // 只显示已审核通过的房源
        wrapper.eq("audit_status", 1);
        wrapper.eq("status", 1);

        // 按创建时间倒序排列
        wrapper.orderByDesc("create_time");

        this.page(page, wrapper);

        // 4. 将搜索结果存入Redis缓存（10分钟过期）
        redisUtil.set(cacheKey, page, CommonConstant.CACHE_SEARCH_EXPIRE);

        return page;
    }

    @Override
    public Page<HouseResourceVo> searchHousesWithPics(String keyword, Integer pageNo, Integer pageSize) {
        Page<HouseResource> page = searchHouses(keyword, pageNo, pageSize);

        // 转换为VO对象并加载图片信息
        List<HouseResourceVo> voList = page.getRecords().stream()
                .map(this::convertToVo)
                .collect(Collectors.toList());

        Page<HouseResourceVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        voPage.setRecords(voList);
        return voPage;
    }

    @Override
    @Transactional
    public boolean updateHouse(HouseResource houseResource) {
        // 检查房源是否存在
        HouseResource existing = this.getById(houseResource.getId());
        if (existing == null) {
            throw new BusinessException("房源不存在");
        }

        // 检查是否有权限修改（房东或管理员）
        Long userId = UserContext.getUserId();
        Integer role = UserContext.getRole();
        if (!existing.getLandlordId().equals(userId) && !Objects.equals(UserRole.ADMIN.getCode(), role)) {
            throw new BusinessException("没有权限修改该房源");
        }

        // 更新房源信息
        return this.updateById(houseResource);
    }

    @Override
    @Transactional
    public boolean deleteHouse(Long id) {
        // 检查房源是否存在
        HouseResource existing = this.getById(id);
        if (existing == null) {
            throw new BusinessException("房源不存在");
        }

        // 检查是否有权限删除（房东或管理员）
        Long userId = UserContext.getUserId();
        Integer role = UserContext.getRole();
        if (!existing.getLandlordId().equals(userId) && !Objects.equals(UserRole.ADMIN.getCode(), role)) {
            throw new BusinessException("没有权限删除该房源");
        }

        // 删除房源（逻辑删除）
        return this.removeById(id);
    }

    @Override
    @Transactional
    public boolean auditHouse(Long id, Integer auditStatus, String auditRemark) {
        // 检查房源是否存在
        HouseResource house = this.getById(id);
        if (house == null) {
            throw new BusinessException("房源不存在");
        }

        // 检查是否有权限审核（只有管理员可以审核）
        Integer role = UserContext.getRole();
        if (!Objects.equals(UserRole.ADMIN.getCode(), role)) {
            throw new BusinessException("只有管理员可以审核房源");
        }

        // 更新审核状态
        house.setAuditStatus(auditStatus);
        house.setAuditTime(new Date());
        house.setAuditUserId(UserContext.getUserId());

        // 如果审核通过，则将状态设为已上架
        if (auditStatus == 1) {
            house.setStatus(1); // 已上架
        } else {
            house.setStatus(0); // 待审核
        }

        boolean result = this.updateById(house);

        // 审核成功后发送消息到消息队列
        if (result) {
            // 使用HouseAuditMessageDto对象来封装消息内容
            HouseAuditMessageDto message = new HouseAuditMessageDto();
            message.setHouseId(id);
            message.setTitle(house.getTitle());
            message.setLandlordId(house.getLandlordId());
            message.setAuditStatus(auditStatus);
            message.setAuditStatusName(getAuditStatusName(auditStatus));
            message.setAuditTime(house.getAuditTime());
            message.setAuditUserId(house.getAuditUserId());

            rabbitTemplate.convertAndSend(
                    CommonConstant.EXCHANGE_NAME,
                    CommonConstant.ROUTING_KEY_HOUSE_AUDIT,
                    message
            );
        }

        return result;
    }

    /**
     * 定时任务：每10分钟将Redis中的浏览量同步到数据库
     */
    @Scheduled(initialDelay = 60_000, fixedDelay = 10 * 60 * 1000)   // 1.8 可用
    public void syncViewCountToDatabase() {
        String pattern = CommonConstant.CACHE_HOUSE_VIEW_COUNT_PREFIX + "*";
        Set<String>        keys   = new HashSet<>();
        // 1. 分批 SCAN 取 KEY
        redisTemplate.execute((RedisCallback<Void>) conn -> {
            try (Cursor<byte[]> cursor = conn.scan(
                    ScanOptions.scanOptions().match(pattern).count(500).build())) {
                while (cursor.hasNext()) {
                    keys.add(new String(cursor.next()));
                }
            } catch (IOException e) {
                log.error("scan 浏览量 key 失败", e);
            }
            return null;
        });

        if (keys.isEmpty()) {
            return;
        }

        // 2. 批量 MGET 取值
        List<Object> values = redisTemplate.opsForValue().multiGet(keys);
        if (values == null || values.isEmpty()) {
            return;
        }

        List<HouseResource> batch = new ArrayList<>(keys.size());
        Iterator<String>    keyIt = keys.iterator();
        for (int i = 0; i < values.size() && keyIt.hasNext(); i++) {
            Object v = values.get(i);
            if (v == null) continue;
            String key   = keyIt.next();
            Long houseId;
            try {
                houseId = Long.valueOf(key.substring(CommonConstant.CACHE_HOUSE_VIEW_COUNT_PREFIX.length()));
            } catch (NumberFormatException e) {
                log.warn("非法 key: {}");
                continue;
            }
            Integer viewCount = Integer.valueOf(v.toString());
            HouseResource hr = new HouseResource();
            hr.setId(houseId);
            hr.setViewCount(viewCount);
            batch.add(hr);
        }

        // 3. 批量更新（MyBatis-Plus 支持）
        if (!batch.isEmpty()) {
            try {
                getBaseMapper().updateBatchViewCount(batch);
                // 4. 成功后再删缓存，避免并发写丢失
                redisTemplate.delete(keys);
            } catch (Exception e) {
                log.error("批量更新浏览量失败", e);
            }
        }
    }

    private String getAuditStatusName(Integer auditStatus) {
        switch (auditStatus) {
            case 0: return "待审核";
            case 1: return "审核通过";
            case 2: return "审核拒绝";
            default: return "未知状态";
        }
    }

    @Override
    @Transactional
    public HouseResourceVo convertToVo(HouseResource houseResource) {
        if (houseResource == null) {
            return null;
        }

        HouseResourceVo vo = new HouseResourceVo();
        BeanUtils.copyProperties(houseResource, vo);

        // 查询并设置图片列表
        QueryWrapper<Pic> picQueryWrapper = new QueryWrapper<>();
        picQueryWrapper.eq("house_id", houseResource.getId());
        picQueryWrapper.orderByAsc("sort");
        List<Pic> picList = picService.list(picQueryWrapper);
        vo.setPicList(picList);

        return vo;
    }

    @Override
    @Transactional
    public Page<HouseResourceVo> getPendingHouses(PendingHouseDto pendingHouseDto) {
        Integer role = UserContext.getUser().getRole();
        if (Objects.equals(role, UserRole.TENANT.getCode())){
            return null;
        }
        Page<HouseResource> page = new Page<>(pendingHouseDto.getPageNo(), pendingHouseDto.getPageSize());
        LambdaQueryWrapper<HouseResource> wrapper = new LambdaQueryWrapper<>();

        wrapper.eq(!ObjectUtils.isEmpty(pendingHouseDto.getAuditStatus()),HouseResource::getAuditStatus, pendingHouseDto.getAuditStatus())
                .eq(!ObjectUtils.isEmpty(pendingHouseDto.getStatus()), HouseResource::getStatus, pendingHouseDto.getStatus())
                .eq(Objects.equals(role, UserRole.LANDLORD.getCode()), HouseResource::getLandlordId, UserContext.getUserId());
        this.page(page, wrapper);
        // 转换为VO对象并加载图片信息
        List<HouseResourceVo> voList = page.getRecords().stream()
                .map(this::convertToVo)
                .collect(Collectors.toList());

        Page<HouseResourceVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        voPage.setRecords(voList);
        return voPage;
    }

}
